# 面向对象编程（Object Oriented Programming，简称OOP）
# 面向过程的程序设计把计算机程序视为一系列的命令集合，即一组函数的顺序执行。为了简化程序设计，
# 面向过程把函数继续切分为子函数，即把大块函数通过切割成小块函数来降低系统的复杂度。

# 面向对象的程序设计把计算机程序视为一组对象的集合，而每个对象都可以接收其他对象发过来的消息，并处理这些消息，
# 计算机程序的执行就是一系列消息在各个对象之间传递。
# 在Python中，所有数据类型都可以视为对象，当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类（Class）的概念
# 类和实例：
# 面向对象最重要的概念就是类（Class）和实例（Instance），必须牢记类是抽象的模板，比如Student类，
# 而实例是根据类创建出来的一个个具体的“对象”，每个对象都拥有相同的方法，但各自的数据可能不同。
# class后面紧接着是类名，即Student，类名通常是大写开头的单词，紧接着是(object)，表示该类是从哪个类继承下来的，
# 继承的概念我们后面再讲，通常，如果没有合适的继承类，就使用object类，这是所有类最终都会继承的类。
# 访问限制：
# 如果要让内部属性不被外部访问，可以把属性的名称前加上两个下划线__，
# 在Python中，实例的变量名如果以__开头，就变成了一个私有变量（private），只有内部可以访问，外部不能访问
# 这样就确保了外部代码不能随意修改对象内部的状态，这样通过访问限制的保护，代码更加健壮。
# 可以通过set，get方法对变量进行设置获取，这点和java也是一样的
# 为什么要定义一个set方法大费周折？因为在方法中，可以对参数做检查，避免传入无效的参数：

class Student(object):
    # __init__方法相当于java中的构造方法，第一个参数永远是self
    def __init__(self, name, age):
        # self.name = name
        # self.age = age
        self.__name = name
        self.__age = age

    def print(self):
        print('%s_%s' % (self.__name, self.__age))

    def get_name(self):
        return self.__name

    def get_age(self):
        return self.__age

    def set_name(self, name):
        self.__name = name

    def set_age(self, age):
        if age > 0:
            self.__age = age
        else:
            self.__age = 0


studentA = Student('aaa', 11)
studentB = Student('bbb', 13)

studentA.print()
studentB.print()
studentA.set_age(-1)
studentA.set_name('AAA')
studentB.set_age(90)
studentA.print()
studentB.print()
print(studentA.get_name())
print(studentA.get_age())
print(studentA)
print(studentB)
print(Student)


# 继承和多态
# 在OOP程序设计中，当我们定义一个class的时候，可以从某个现有的class继承，新的class称为子类（Subclass），而被继承的class称为基类、父类或超类（Base class、Super class）
class Animal(object):
    def run(self):
        print(self.__class__.__name__, 'is running')


class Dog(Animal):
    pass


class Cat(Animal):
    # 可以对子类增加一些方法
    def eat(self):
        print('Cat.eat')

    # 也可以覆盖父类的方法，在代码运行的时候，总是会调用子类的run()
    # 这样，我们就获得了继承的另一个好处：多态。
    def run(self):
        print('Cat.run:jump...')


animal = Animal()
animal.run()
dog = Dog()
dog.run()
cat = Cat()
cat.run()
cat.eat()

print(isinstance(dog, Dog))
print(isinstance(dog, Cat))
print(isinstance(dog, Animal))
print(isinstance(dog, object))


def run_twice(animal):
    animal.run()
    animal.run()


run_twice(cat)
run_twice(dog)


# 你会发现，新增一个Animal的子类，不必对run_twice()做任何修改，实际上，
# 任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行，原因就在于多态。
class Monkey(Animal):
    pass


run_twice(Monkey())


# 对于一个变量，我们只需要知道它是Animal类型，无需确切地知道它的子类型，就可以放心地调用run()方法，
# 而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上，由运行时该对象的确切类型决定，这就是多态真正的威力：
# 调用方只管调用，不管细节，而当我们新增一种Animal的子类时，只要确保run()方法编写正确，不用管原来的代码是如何调用的。
# 这就是著名的“开闭”原则：
# 对扩展开放：允许新增Animal子类；
# 对修改封闭：不需要修改依赖Animal类型的run_twice()等函数。


# 静态语言动态语言
# 对于静态语言（例如Java）来说，如果需要传入Animal类型，则传入的对象必须是Animal类型或者它的子类，否则，将无法调用run()方法。
# 对于Python这样的动态语言来说，则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了：
# 这就是动态语言的“鸭子类型”，它并不要求严格的继承体系，一个对象只要“看起来像鸭子，走起路来像鸭子”，那它就可以被看做是鸭子。
class Timer(object):
    def run(self):
        print('Timer.run:di da di da')


run_twice(Timer())

# 获取对象信息
# 1. 使用type()判断对象类型
print('使用type:')
print(type(dog))
print(type(cat))
print(type(Timer()))
print(type(11))
print(type('11'))
print(type(123) == type(456))
print(type(123) == int)
print(type('11') == type(11))
# 判断基本数据类型可以直接写int，str等，但如果要判断一个对象是否是函数怎么办？可以使用types模块中定义的常量：
print('使用type+types:')
import types


def fn():
    pass


print(type(fn) == types.FunctionType)
print(type(abs) == types.FunctionType)
print(type(lambda x: x * x) == types.FunctionType)
print(type(x + 100 for x in range(10)) == types.FunctionType)
# 2. 使用isinstance()
# 对于class的继承关系来说，使用type()就很不方便。我们要判断class的类型，可以使用isinstance()函数。
print('使用isinstance:')
print(isinstance(dog, Dog))
print(isinstance(fn, object))
print(isinstance(11, object))
print(isinstance('11', object))
# 还可以判断一个变量是否是某些类型中的一种
print(isinstance('11', (int, str)))

# 3. 使用dir()
print('使用dir:')
# 如果要获得一个对象的所有属性和方法，可以使用dir()函数，它返回一个包含字符串的list，比如，获得一个str对象的所有属性和方法：
print(dir(123))
print(dir('abc'))
print(dir(dog))
print(dir(studentA))
# 类似__xxx__的属性和方法在Python中都是有特殊用途的，比如__len__方法返回长度。在Python中，如果你调用len()函数试图获取
# 一个对象的长度，实际上，在len()函数内部，它自动去调用该对象的__len__()方法，所以，下面的代码是等价的：
print(len('abc'))
print('abc'.__len__())
# 剩下的都是普通属性或方法，比如lower()返回小写的字符串：
print('ABC'.lower())


# 仅仅把属性和方法列出来是不够的，配合getattr()、setattr()以及hasattr()，我们可以直接操作一个对象的状态：
class MyObj(object):
    def __init__(self):
        self.x = 9

    def power(self):
        return self.x * self.x


obj = MyObj()
# 有属性'x'吗？
print(hasattr(obj, 'x'))
# 有属性'y'吗？
print(hasattr(obj, 'y'))
# 设置一个属性'y'
setattr(obj, 'y', 19)
# 获取属性'y'
print(getattr(obj, 'y'))
print(obj.y)
print(obj.x)
# 有属性'y'吗？
print(hasattr(obj, 'y'))
# 可以传入一个default参数，如果属性不存在，就返回默认值：
print(getattr(obj, 'z', 404))
# 也可以获得对象的方法：
print(hasattr(obj, 'power'))
print(getattr(obj, 'power'))
fn = getattr(obj, 'power')
print(fn)
print(fn())


# 实例属性和类属性
# 由于Python是动态语言，根据类创建的实例可以任意绑定属性。
# 给实例绑定属性的方法是通过实例变量，或者通过self变量：
# 但是，如果Student类本身需要绑定一个属性呢？可以直接在class中定义属性，这种属性是类属性，归Student类所有
class Person(object):
    tag = 'tag person'

    def __init__(self, name):
        self.name = name


personA = Person('AAA')
personA.age = 21
print(personA.name)
print(personA.age)
print(personA.tag)
print(Person.tag)
print(Person.__name__)
# 由于实例属性优先级比类属性高，因此，它会屏蔽掉类的tag属性
setattr(personA, 'tag', 'aaa')
print(personA.tag)
print(Person.tag)
# 如果删除实例的name属性
del personA.tag
print(personA.tag)
print(Person.tag)
